﻿//using Microsoft.CodeAnalysis.CSharp.Syntax;
using OpenCvSharp;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System;
using OpenCvSharp.Extensions;
using ZXing;
using ZXing.Common;
using ZXing.Windows.Compatibility;
using System.Windows.Forms;
using System.IO;
using System.Text;
using Size = OpenCvSharp.Size;
using Point = OpenCvSharp.Point;

namespace ACLib.Utilities.Libs
{
    public class ChoiceSubjectOCRAnalyzer
    {
        public OpenCvSharp.Size dotSize = new OpenCvSharp.Size();
        public double dotSpacing = 0;
        public List<Rect> rects=new List<Rect>();
        public Mat tempMat=new Mat();
        public ChoiceSubjectOCRAnalyzer()
        {
        }
       public  static Mat 二值化(Mat src)
        { 
            // 2. 灰度
            Mat gray = new Mat();
            Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);           
            // 3. 轻度高斯去噪（可选）
            Cv2.GaussianBlur(gray, gray, new Size(1, 1), 2);            
            // 4. 自适应阈值保留细节
            // 1. 全局阈值兜底（保留黑块）
            Mat globalBin = new Mat();
            Cv2.Threshold(gray, globalBin, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);           
            // 2. 自适应阈值提细节（小窗口，避开黑块）
            Mat adaptBin = new Mat();
            Cv2.AdaptiveThreshold(gray, adaptBin, 255,
                                  AdaptiveThresholdTypes.GaussianC,
                                  ThresholdTypes.Binary, 35, 15);         
            // 3. 合并：全局为黑就保持黑，否则用自适应结果
            Mat final = globalBin & adaptBin;   // 逐像素或操作，黑块恢复实心
          
            
            return final;
           
        }
        public void 体块解析(List<Bitmap> bitmaps)
        {
            Mat mat = new Mat();
            List<Size> BlockSizes = new List<Size>();
            List<double> Spacings = new List<double>();
            nswersheetHelper nsh = new nswersheetHelper();
            List<List<DetectedRect>> llr = new List<List<DetectedRect>>();
            foreach (Bitmap bitmap in bitmaps)
            {
                List<DetectedRect> dlr = new List<DetectedRect>();             
                nsh.SourceImage = bitmap.ToMat();
                BlockSizes.Add(nsh.BlockSize);
                Spacings.Add(nsh.Spacing);
                Mat mat1=new Mat();
                 mat=bitmap.ToMat();
                Cv2.BitwiseNot(mat, mat1);
                var rs = MatSplitter.SplitHV(bitmap.ToMat(), 3);
                var nrs = ImageBlockProcessor.RemoveWhiteBordersFromRects(mat1, rs);
                for (int i = 0; i < nrs.Count; i++)
                {
                    DetectedRect dr = new DetectedRect();
                    dr.X = nrs[i].X;
                    dr.Y = nrs[i].Y;
                    dr.Width = nrs[i].Width;
                    dr.Height = nrs[i].Height;
                    dlr.Add(dr);
                   // mat.Rectangle(nrs[i], Scalar.Blue);
                }
                llr.Add(dlr);
            }
            var re = nswersheetHelper.CalculateAverages(Spacings, BlockSizes);
            dotSize = re.averageBlockSize;
            dotSpacing = re.averageSpacing;            
            RobustRectMerger rrm = new RobustRectMerger(1010, 290);
            var rm = rrm.MergeRects(llr);
            foreach (var r in rm)
            {
                Rect rect1 = new Rect();
                rect1.X = (int)r.X; rect1.Y = (int)r.Y; rect1.Width = (int)r.Width; rect1.Height = (int)r.Height;
                rects.Add(rect1);
                tempMat = mat;
                tempMat.Rectangle(rect1, Scalar.Green);
            }

            //this.rects = new List<Rect>();
        }

        public string GetImageContent(Bitmap bitmap)
        {
            string 答案 = "";
            tempMat = bitmap.ToMat();
            int k = 1;
            for (int i = 0; i < rects.Count; i++)
            {
               tempMat.Rectangle(rects[i], Scalar.Blue);
                小题 XT = new 小题(dotSize, dotSpacing);
                XT.Process(tempMat.Clone(rects[i]));
                var xtanswers = XT.Answers.ToList();
                foreach (var it in xtanswers)
                {
                    答案 += $"{k++}:{it},";
                }

            }
            return 答案;
        }

        #region 整合的辅助类
        public struct DetectedRect
        {
            public float X;
            public float Y;
            public float Width;
            public float Height;

            public DetectedRect(float x, float y, float width, float height)
            {
                X = x;
                Y = y;
                Width = width;
                Height = height;
            }

            public float CenterX => X + Width / 2f;
            public float CenterY => Y + Height / 2f;
            public float Area => Width * Height;
        }

        public class RobustRectMerger
        {
            private readonly float _imgWidth;
            private readonly float _imgHeight;
            private readonly float _positionTolerance;
            private readonly float _sizeTolerance;
            private readonly float _aspectTolerance;
            private readonly int _minOccurrences;

            public RobustRectMerger(float imgWidth, float imgHeight,
                                  float positionTolerance = 0.15f,
                                  float sizeTolerance = 0.4f,
                                  float aspectTolerance = 0.3f,
                                  int minOccurrences = 5)
            {
                _imgWidth = imgWidth;
                _imgHeight = imgHeight;
                _positionTolerance = Math.Clamp(positionTolerance, 0.05f, 0.3f);
                _sizeTolerance = Math.Clamp(sizeTolerance, 0.2f, 0.8f);
                _aspectTolerance = Math.Clamp(aspectTolerance, 0.1f, 0.5f);
                _minOccurrences = Math.Max(minOccurrences, 1);
            }

            public List<DetectedRect> MergeRects(List<List<DetectedRect>> rectLists)
            {
                if (rectLists == null || rectLists.Count == 0 || rectLists.All(list => list.Count == 0))
                    return new List<DetectedRect>();

                var allRects = new List<(DetectedRect rect, int sourceIndex)>();
                for (int i = 0; i < rectLists.Count; i++)
                {
                    foreach (var rect in rectLists[i])
                    {
                        allRects.Add((rect, i));
                    }
                }

                if (allRects.Count == 0) return new List<DetectedRect>();
                var groups = CreateSizeAgnosticGroups(allRects);
                var mergedRects = MergeGroups(groups);
                mergedRects = mergedRects
                    .OrderBy(r => r.Y)
                    .ThenBy(r => r.X)
                    .ToList();

                return mergedRects;
            }

            private List<List<(DetectedRect rect, int sourceIndex)>> CreateSizeAgnosticGroups(
                List<(DetectedRect rect, int sourceIndex)> allRects)
            {
                var positionGroups = new List<List<(DetectedRect rect, int sourceIndex)>>();
                var positionVisited = new bool[allRects.Count];

                for (int i = 0; i < allRects.Count; i++)
                {
                    if (positionVisited[i]) continue;

                    var group = new List<(DetectedRect rect, int sourceIndex)> { allRects[i] };
                    positionVisited[i] = true;

                    for (int j = i + 1; j < allRects.Count; j++)
                    {
                        if (positionVisited[j]) continue;

                        if (IsPositionSimilar(allRects[i].rect, allRects[j].rect))
                        {
                            group.Add(allRects[j]);
                            positionVisited[j] = true;
                        }
                    }

                    positionGroups.Add(group);
                }

                var finalGroups = new List<List<(DetectedRect rect, int sourceIndex)>>();
                foreach (var posGroup in positionGroups)
                {
                    if (posGroup.All(r => r.sourceIndex == posGroup[0].sourceIndex))
                    {
                        finalGroups.Add(posGroup);
                        continue;
                    }

                    var sizeGroups = new List<List<(DetectedRect rect, int sourceIndex)>>();
                    var sizeVisited = new bool[posGroup.Count];

                    for (int i = 0; i < posGroup.Count; i++)
                    {
                        if (sizeVisited[i]) continue;

                        var sizeGroup = new List<(DetectedRect rect, int sourceIndex)> { posGroup[i] };
                        sizeVisited[i] = true;

                        for (int j = i + 1; j < posGroup.Count; j++)
                        {
                            if (sizeVisited[j]) continue;

                            if (IsSizeSimilar(posGroup[i].rect, posGroup[j].rect))
                            {
                                sizeGroup.Add(posGroup[j]);
                                sizeVisited[j] = true;
                            }
                        }

                        sizeGroups.Add(sizeGroup);
                    }

                    finalGroups.AddRange(sizeGroups);
                }

                return finalGroups;
            }

            private bool IsPositionSimilar(DetectedRect a, DetectedRect b)
            {
                float centerDist = Distance(a.CenterX, a.CenterY, b.CenterX, b.CenterY);
                float avgSize = (a.Width + a.Height + b.Width + b.Height) / 4f;
                float maxDist = avgSize * _positionTolerance;
                return centerDist <= maxDist;
            }

            private bool IsSizeSimilar(DetectedRect a, DetectedRect b)
            {
                float widthRatio = Math.Abs(a.Width - b.Width) / Math.Max(a.Width, b.Width);
                float heightRatio = Math.Abs(a.Height - b.Height) / Math.Max(a.Height, b.Height);
                float aspectA = a.Width / Math.Max(1, a.Height);
                float aspectB = b.Width / Math.Max(1, b.Height);
                float aspectRatio = Math.Abs(aspectA - aspectB) / Math.Max(aspectA, aspectB);

                return widthRatio <= _sizeTolerance &&
                       heightRatio <= _sizeTolerance &&
                       aspectRatio <= _aspectTolerance;
            }

            private float Distance(float x1, float y1, float x2, float y2)
            {
                float dx = x2 - x1;
                float dy = y2 - y1;
                return (float)Math.Sqrt(dx * dx + dy * dy);
            }

            private List<DetectedRect> MergeGroups(List<List<(DetectedRect rect, int sourceIndex)>> groups)
            {
                var mergedRects = new List<DetectedRect>();
                foreach (var group in groups)
                {
                    var uniqueSources = group.Select(r => r.sourceIndex).Distinct().Count();
                    if (uniqueSources < _minOccurrences) continue;

                    float medianX = group.Select(r => r.rect.CenterX).Median();
                    float medianY = group.Select(r => r.rect.CenterY).Median();
                    float medianWidth = group.Select(r => r.rect.Width).Median();
                    float medianHeight = group.Select(r => r.rect.Height).Median();
                    float x = medianX - medianWidth / 2f;
                    float y = medianY - medianHeight / 2f;

                    mergedRects.Add(new DetectedRect(x, y, medianWidth, medianHeight));
                }
                return mergedRects;
            }
        }

        //public static class EnumerableExtensions
        //{
        //    public static float Median(this IEnumerable<float> source)
        //    {
        //        var sorted = source.OrderBy(n => n).ToArray();
        //        int count = sorted.Length;

        //        if (count == 0) return 0;
        //        if (count % 2 == 0)
        //        {
        //            return (sorted[count / 2 - 1] + sorted[count / 2]) / 2f;
        //        }
        //        return sorted[count / 2];
        //    }
        //}

        public enum SplitDirection { Horizontal, Vertical }

        public class SplitResult
        {
            public Mat Image { get; set; }
            public Rect ROI { get; set; }

            public SplitResult(Mat image, Rect roi)
            {
                Image = image;
                ROI = roi;
            }
        }

        public class MatSplitter
        {
            public struct GapInfo
            {
                public int Start { get; }
                public int Height { get; }

                public GapInfo(int start, int height)
                {
                    Start = start;
                    Height = height;
                }
            }

            public static Mat EnsureBinary(Mat input)
            {
                if (input.Channels() == 1 && input.Type() == MatType.CV_8UC1)
                {
                    double minVal, maxVal;
                    Cv2.MinMaxLoc(input, out minVal, out maxVal);
                    if (minVal == 0 && maxVal == 255)
                        return input.Clone();
                }

                Mat gray = new Mat();
                if (input.Channels() == 3)
                {
                    Cv2.CvtColor(input, gray, ColorConversionCodes.BGR2GRAY);
                }
                else if (input.Channels() == 4)
                {
                    Cv2.CvtColor(input, gray, ColorConversionCodes.BGRA2GRAY);
                }
                else
                {
                    gray = input.Clone();
                }

                Mat binary = new Mat();
                Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
                if (gray != input) gray.Dispose();
                return binary;
            }

            public static List<Rect> SplitHV(Mat src, int maxHorizontal = 13, int maxVertical = int.MaxValue)
            {
                var finalRects = new List<Rect>();
                var src1 = EnsureBinary(src);
                var splitter = new MatSplitter();
                var hBlocks = splitter.SplitMat(src, SplitDirection.Horizontal, maxHorizontal);

                foreach (var h in hBlocks)
                {
                    Mat hRoiMat = src.Clone(h.ROI);
                    var vBlocks = splitter.SplitMat(hRoiMat, SplitDirection.Vertical, maxVertical);
                    foreach (var v in vBlocks)
                    {
                        Rect absRect = new Rect(h.ROI.X + v.ROI.X, h.ROI.Y + v.ROI.Y, v.ROI.Width, v.ROI.Height);
                        finalRects.Add(absRect);
                    }
                }
                return finalRects;
            }

            public List<SplitResult> SplitMat(Mat src, SplitDirection direction, int maxParts)
            {
                if (src.Empty())
                    return new List<SplitResult> { new SplitResult(src.Clone(), new Rect(0, 0, src.Width, src.Height)) };

                if (maxParts < 1)
                    throw new ArgumentException("maxParts必须大于0");

                using Mat gray = src.Channels() == 3 ? src.CvtColor(ColorConversionCodes.BGR2GRAY) : src.Clone();
                using Mat binary = PreprocessImage(gray);
                return direction == SplitDirection.Horizontal
                    ? SplitHorizontally(binary, maxParts, src)
                    : SplitVertically(binary, src);
            }

            private Mat PreprocessImage(Mat gray)
            {
                bool isWhiteBackground = DetectBackgroundColor(gray);
                Mat binary = new Mat();
                Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
                if (isWhiteBackground) Cv2.BitwiseNot(binary, binary);
                return binary;
            }

            private bool DetectBackgroundColor(Mat gray)
            {
                int borderSize = 5;
                int whiteCount = 0;
                int totalCorners = 0;

                for (int y = 0; y < borderSize; y++)
                {
                    for (int x = 0; x < borderSize; x++)
                    {
                        if (gray.At<byte>(y, x) > 200) whiteCount++;
                        if (gray.At<byte>(y, gray.Width - 1 - x) > 200) whiteCount++;
                        if (gray.At<byte>(gray.Height - 1 - y, x) > 200) whiteCount++;
                        if (gray.At<byte>(gray.Height - 1 - y, gray.Width - 1 - x) > 200) whiteCount++;
                        totalCorners += 4;
                    }
                }
                return (whiteCount * 100 / totalCorners) > 70;
            }

            private List<SplitResult> SplitHorizontally(Mat binary, int maxParts, Mat original)
            {
                int[] projection = CalculateProjection(binary, true);
                int[] smoothed = SmoothProjection(projection, 2);
                var gaps = FindGaps(smoothed, 2, 10);
                var gapsH = FindSignificantlyHigherGaps(gaps);

                if (gapsH.Count == 0 || gapsH.Count >= maxParts)
                    return new List<SplitResult> { new SplitResult(original.Clone(), new Rect(0, 0, original.Width, original.Height)) };

                var result = new List<SplitResult>();
                int startY = 0;

                foreach (var gap in gapsH.OrderBy(g => g.Start))
                {
                    int height = gap.Start - startY;
                    if (height > 0)
                    {
                        Rect roi = new Rect(0, startY, original.Width, height);
                        result.Add(new SplitResult(new Mat(original, roi), roi));
                    }
                    startY = gap.Start + gap.Height;
                }

                if (startY < binary.Height)
                {
                    Rect roi = new Rect(0, startY, original.Width, original.Height - startY);
                    result.Add(new SplitResult(new Mat(original, roi), roi));
                }

                return result;
            }

            private List<SplitResult> SplitVertically(Mat binary, Mat original)
            {
                int[] projection = CalculateProjection(binary, false);
                int[] smoothed = SmoothProjection(projection, 2);
                var gaps = FindGaps(smoothed, 2, 2);
                gaps = FindSignificantlyHigherGaps(gaps);
                if (gaps.Count == 0)
                    return new List<SplitResult> { new SplitResult(original.Clone(), new Rect(0, 0, original.Width, original.Height)) };

                var result = new List<SplitResult>();
                int startX = 0;

                foreach (var gap in gaps.OrderBy(g => g.Start))
                {
                    int width = gap.Start - startX;
                    if (width > 0)
                    {
                        Rect roi = new Rect(startX, 0, width, original.Height);
                        result.Add(new SplitResult(new Mat(original, roi), roi));
                    }
                    startX = gap.Start + gap.Height;
                }

                if (startX < binary.Width)
                {
                    Rect roi = new Rect(startX, 0, original.Width - startX, original.Height);
                    result.Add(new SplitResult(new Mat(original, roi), roi));
                }

                return result;
            }

            private int[] CalculateProjection(Mat binary, bool isHorizontal)
            {
                int size = isHorizontal ? binary.Height : binary.Width;
                int[] projection = new int[size];

                for (int i = 0; i < size; i++)
                {
                    using Mat line = isHorizontal ? binary.Row(i) : binary.Col(i);
                    projection[i] = Cv2.CountNonZero(line);
                }
                return projection;
            }

            private int[] SmoothProjection(int[] projection, int kernelSize)
            {
                if (kernelSize < 3) return projection;
                int[] smoothed = new int[projection.Length];
                int radius = kernelSize / 2;

                for (int i = 0; i < projection.Length; i++)
                {
                    int sum = 0;
                    int count = 0;

                    for (int j = -radius; j <= radius; j++)
                    {
                        int idx = i + j;
                        if (idx >= 0 && idx < projection.Length)
                        {
                            sum += projection[idx];
                            count++;
                        }
                    }
                    smoothed[i] = sum / count;
                }
                return smoothed;
            }

            private List<GapInfo> FindGaps(int[] projection, int minGapHeight, int minSegmentHeight)
            {
                var gaps = new List<GapInfo>();
                double avg = projection.Average();
                int threshold = (int)(avg * 0.1);
                bool inGap = false;
                int gapStart = 0;
                int segmentStart = 0;

                for (int i = 0; i < projection.Length; i++)
                {
                    if (projection[i] <= threshold)
                    {
                        if (!inGap)
                        {
                            inGap = true;
                            gapStart = i;
                        }
                    }
                    else
                    {
                        if (inGap)
                        {
                            int gapHeight = i - gapStart;
                            if (gapHeight >= minGapHeight)
                            {
                                int segmentHeight = gapStart - segmentStart;
                                if (segmentHeight >= minSegmentHeight)
                                {
                                    gaps.Add(new GapInfo(gapStart, gapHeight));
                                }
                                segmentStart = i;
                            }
                            inGap = false;
                        }
                    }
                }
                return gaps;
            }

            public static List<GapInfo> FindSignificantlyHigherGaps(IEnumerable<GapInfo> gaps, double thresholdInStd = 2.0)
            {
                if (gaps == null || !gaps.Any()) return new List<GapInfo>();
                var list = gaps.ToList();
                var heights = list.Select(g => (double)g.Height).ToList();
                double avg = heights.Average();
                double std = Math.Sqrt(heights.Select(h => Math.Pow(h - avg, 2)).Average());
                if (std == 0) return new List<GapInfo>();
                double upperBound = avg + thresholdInStd * std;
                return list.Where(g => g.Height > upperBound).ToList();
            }
        }

        public class ImageBlockProcessor
        {
            public static Mat ConvertToBinaryConservative(Mat input)
            {
                using Mat gray = new Mat();
                Cv2.CvtColor(input, gray, ColorConversionCodes.BGR2GRAY);
                using Mat blurred = new Mat();
                Cv2.GaussianBlur(gray, blurred, new Size(3, 3), 0.5);
                using Mat binary = new Mat();
                double contrast = Cv2.Mean(blurred).Val0;

                if (contrast > 100)
                {
                    Cv2.Threshold(blurred, binary, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
                }
                else
                {
                    Cv2.AdaptiveThreshold(blurred, binary, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 21, 8);
                }
                return binary.Clone();
            }

            public static Mat InvertImage(Mat input)
            {
                Mat output = new Mat();
                Cv2.BitwiseNot(input, output);
                return output;
            }

            public static Mat DilateImage(Mat input, int width, int height)
            {
                Mat output = new Mat();
                Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(width, height));
                Cv2.Dilate(input, output, kernel);
                return output;
            }

            public static List<Rect> GetConnectedBlocks(Mat binaryImage)
            {
                Mat processed = new Mat();
                if (binaryImage.Channels() > 1)
                {
                    Cv2.CvtColor(binaryImage, processed, ColorConversionCodes.BGR2GRAY);
                }
                else
                {
                    processed = binaryImage.Clone();
                }

                Mat labels = new Mat();
                Mat stats = new Mat();
                Mat centroids = new Mat();
                int numLabels = Cv2.ConnectedComponentsWithStats(processed, labels, stats, centroids);
                List<Rect> blocks = new List<Rect>();
                for (int i = 1; i < numLabels; i++)
                {
                    blocks.Add(new Rect(stats.At<int>(i, 0), stats.At<int>(i, 1), stats.At<int>(i, 2), stats.At<int>(i, 3)));
                }
                return blocks.OrderBy(b => b.Top / 40).ThenBy(b => b.Left).ToList();
            }

            public static List<Rect> RemoveWhiteBordersFromRects(Mat src, List<Rect> rects, int noiseSize = 0)
            {
                var resultRects = new List<Rect>();
                foreach (var rect in rects)
                {
                    using var roi = new Mat(src, rect);
                    using var mask = new Mat();
                    Cv2.InRange(roi, new Scalar(250, 250, 250), new Scalar(255, 255, 255), mask);
                    using var denoisedMask = new Mat();
                    var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(1, 1));
                    Cv2.MorphologyEx(mask, denoisedMask, MorphTypes.Open, kernel, iterations: 3);
                    //Cv2.BitwiseNot(denoisedMask, denoisedMask);
                    var points = denoisedMask.FindNonZero();

                    if (points.Empty())
                    {
                        resultRects.Add(rect);
                    }
                    else
                    {
                        var contentRect = Cv2.BoundingRect(points);
                        var globalRect = new Rect(rect.X + contentRect.X, rect.Y + contentRect.Y, contentRect.Width, contentRect.Height);
                        resultRects.Add(globalRect);
                    }
                }
                return resultRects;
            }
        }

        public static class NoiseCleaner
        {
            public static Mat CleanWhiteNoise(Mat src, int noiseMaxSize = 10, int kernelSize = 3)
            {
                using var gray = src.Channels() == 3 ? src.CvtColor(ColorConversionCodes.BGR2GRAY) : src.Clone();
                using var binary = new Mat();
                Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
                using var cleaned = new Mat();
                CleanByArea(binary, cleaned, noiseMaxSize, kernelSize);
                return cleaned;
            }

            private static void CleanByArea(Mat binary, Mat output, int maxNoiseSize, int kernelSize)
            {
                using var labels = new Mat();
                using var stats = new Mat();
                using var centroids = new Mat();
                int numLabels = Cv2.ConnectedComponentsWithStats(binary, labels, stats, centroids);
                output.SetTo(Scalar.Black);

                for (int i = 1; i < numLabels; i++)
                {
                    int area = stats.At<int>(i, (int)ConnectedComponentsTypes.Area);
                    if (area > maxNoiseSize)
                    {
                        int x = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
                        int y = stats.At<int>(i, (int)ConnectedComponentsTypes.Top);
                        int width = stats.At<int>(i, (int)ConnectedComponentsTypes.Width);
                        int height = stats.At<int>(i, (int)ConnectedComponentsTypes.Height);
                        var roi = new Rect(x, y, width, height);
                        using var component = new Mat(binary, roi);
                        using var dstRoi = new Mat(output, roi);
                        component.CopyTo(dstRoi);
                    }
                }
            }
        }

        internal class nswersheetHelper
        {
            public static string[] GetFiles()
            {
                string folder = @"D:\Oamist\PIC";
                return Directory.GetFiles(folder, "*.*")
                    .Where(f => f.EndsWith(".jpg") || f.EndsWith(".png") || f.EndsWith(".bmp"))
                    .ToArray();
            }

            public static string 识别条形码(System.Drawing.Bitmap filename)
            {
                BarcodeReader reader = new BarcodeReader();
                reader.Options = new DecodingOptions
                {
                    CharacterSet = "UTF-8",
                    TryHarder = true,
                    PossibleFormats = new List<BarcodeFormat> {
                        BarcodeFormat.QR_CODE,
                        BarcodeFormat.CODE_128,
                        BarcodeFormat.CODE_39,
                        BarcodeFormat.EAN_8,
                        BarcodeFormat.EAN_13
                    }
                };
                Result result = reader.Decode(filename);
                return result?.Text ?? "";
            }

            public static Mat DrawBlockNumbers(Mat background, List<Rect> blocks, int groupTolerance = 20)
            {
                Mat result = background.Channels() == 1
                    ? background.CvtColor(ColorConversionCodes.GRAY2BGR)
                    : background.Clone();

                if (blocks == null || !blocks.Any())
                {
                    Cv2.PutText(result, "No blocks detected", new Point(10, 30), HersheyFonts.HersheySimplex, 0.8, Scalar.Red, 2);
                    return result;
                }

                Scalar[] palette = {
                    Scalar.FromRgb(255, 100, 100),
                    Scalar.FromRgb(100, 255, 100),
                    Scalar.FromRgb(100, 100, 255),
                    Scalar.FromRgb(255, 255, 100),
                    Scalar.FromRgb(255, 100, 255),
                    Scalar.FromRgb(100, 255, 255)
                };

                for (int i = 0; i < blocks.Count; i++)
                {
                    Rect block = blocks[i];
                    int groupKey = block.Y / groupTolerance;
                    Scalar color = palette[groupKey % palette.Length];

                    using (Mat overlay = result.Clone())
                    {
                        Cv2.Rectangle(overlay, block, color, -1);
                        Cv2.AddWeighted(overlay, 0.2, result, 0.8, 0, result);
                    }

                    Cv2.Rectangle(result, block, color, 2);
                    double fontSize = Math.Clamp(block.Height / 30.0, 0.5, 20);
                    Point textPos = new Point(block.X + 5, block.Y + block.Height - 5);

                    Cv2.PutText(result, $"{i + 1}", textPos + new Point(1, 1), HersheyFonts.HersheySimplex, fontSize, Scalar.Black, 2);
                    Cv2.PutText(result, $"{i + 1}", textPos, HersheyFonts.HersheySimplex, fontSize, Scalar.White, 1);
                }

                int groupCount = blocks.GroupBy(b => b.Y / groupTolerance).Count();
                Cv2.PutText(result, $"Blocks: {blocks.Count} | Groups: {groupCount}", new Point(10, 30), HersheyFonts.HersheySimplex, 0.7, Scalar.Yellow, 2);
                return result;
            }

            public double Spacing { get; private set; }
            public Size BlockSize { get; private set; }

            private Mat _sourceImage;
            public Mat SourceImage
            {
                get => _sourceImage;
                set
                {
                    _sourceImage = value?.Clone();
                    if (_sourceImage != null) ProcessImage();
                }
            }

            private void ProcessImage()
            {
                Mat binaryImage = EnsureBinary(_sourceImage);
                Cv2.BitwiseNot(binaryImage, binaryImage);
                Mat erodedImage = ApplyMinimumFilter(binaryImage, 1);
                List<Rect> boundingRects = FindAllBoundingRects(erodedImage);
                Spacing = CalculateHorizontalSpacingMode(boundingRects);
                BlockSize = CalculateBlockSize(boundingRects);
                binaryImage.Dispose();
                erodedImage.Dispose();
            }

            public Mat EnsureBinary(Mat input)
            {
                if (input.Channels() == 1 && input.Type() == MatType.CV_8UC1)
                {
                    double minVal, maxVal;
                    Cv2.MinMaxLoc(input, out minVal, out maxVal);
                    if (minVal == 0 && maxVal == 255) return input.Clone();
                }

                Mat gray = new Mat();
                if (input.Channels() == 3) Cv2.CvtColor(input, gray, ColorConversionCodes.BGR2GRAY);
                else if (input.Channels() == 4) Cv2.CvtColor(input, gray, ColorConversionCodes.BGRA2GRAY);
                else gray = input.Clone();

                Mat binary = new Mat();
                Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
                if (gray != input) gray.Dispose();
                return binary;
            }

            public Mat ApplyMinimumFilter(Mat binaryImage, int size)
            {
                Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(2 * size + 1, 2 * size + 1), new OpenCvSharp.Point(size, size));
                Mat eroded = new Mat();
                Cv2.Erode(binaryImage, eroded, kernel, iterations: 1);
                kernel.Dispose();
                return eroded;
            }

            private List<Rect> FindAllBoundingRects(Mat binaryImage)
            {
                List<Rect> boundingRects = new List<Rect>();
                Point[][] contours;
                HierarchyIndex[] hierarchy;
                Cv2.FindContours(binaryImage, out contours, out hierarchy, RetrievalModes.List, ContourApproximationModes.ApproxSimple);

                foreach (var contour in contours)
                {
                    double area = Cv2.ContourArea(contour);
                    if (area < 10) continue;
                    Rect rect = Cv2.BoundingRect(contour);
                    boundingRects.Add(rect);
                }
                return boundingRects;
            }

            private double CalculateHorizontalSpacingMode(List<Rect> rects)
            {
                if (rects.Count < 2) return 0;
                var sortedRects = rects.OrderBy(r => r.X).ToList();
                List<double> spacings = new List<double>();

                for (int i = 1; i < sortedRects.Count; i++)
                {
                    Rect prev = sortedRects[i - 1];
                    Rect current = sortedRects[i];
                    double spacing = current.X - (prev.X + prev.Width);
                    if (spacing > 0) spacings.Add(spacing);
                }

                if (spacings.Count == 0) return 0;
                Dictionary<int, int> frequencyMap = new Dictionary<int, int>();
                foreach (double spacing in spacings)
                {
                    int rounded = (int)Math.Round(spacing);
                    if (frequencyMap.ContainsKey(rounded)) frequencyMap[rounded]++;
                    else frequencyMap[rounded] = 1;
                }

                var mode = frequencyMap.OrderByDescending(kv => kv.Value).First();
                return mode.Key;
            }

            private Size CalculateBlockSize(List<Rect> rects)
            {
                if (rects == null || rects.Count == 0) return new Size(0, 0);
                int widthMode = CalculateMode(rects.Select(r => r.Width));
                int heightMode = CalculateMode(rects.Select(r => r.Height));
                return new Size(widthMode, heightMode);
            }

            private int CalculateMode(IEnumerable<int> values)
            {
                var groups = values.GroupBy(v => v).OrderByDescending(g => g.Count()).ToList();
                return groups.Count > 0 ? groups.First().Key : 0;
            }

            public static (double averageSpacing, Size averageBlockSize) CalculateAverages(
                List<double> spacings, List<Size> blockSizes)
            {
                if (spacings == null || blockSizes == null) throw new ArgumentNullException("Input lists cannot be null.");
                if (spacings.Count != blockSizes.Count) throw new ArgumentException("Both lists must have the same count.");
                if (spacings.Count == 0) return (0, new Size());
                List<int> validIndices = GetValidIndices(spacings);
                return CalculateValidAverages(spacings, blockSizes, validIndices);
            }

            private static List<int> GetValidIndices(List<double> spacings)
            {
                var sorted = new List<double>(spacings);
                sorted.Sort();
                double median = sorted[sorted.Count / 2];
                if (sorted.Count % 2 == 0) median = (sorted[sorted.Count / 2 - 1] + sorted[sorted.Count / 2]) / 2.0;

                List<double> deviations = new List<double>();
                foreach (double val in spacings) deviations.Add(Math.Abs(val - median));
                deviations.Sort();
                double mad = deviations[deviations.Count / 2];
                if (deviations.Count % 2 == 0) mad = (deviations[deviations.Count / 2 - 1] + deviations[deviations.Count / 2]) / 2.0;

                double lowerBound = median - 3 * 1.4826 * mad;
                double upperBound = median + 3 * 1.4826 * mad;
                List<int> validIndices = new List<int>();

                for (int i = 0; i < spacings.Count; i++)
                {
                    if (mad == 0 || (spacings[i] >= lowerBound && spacings[i] <= upperBound))
                        validIndices.Add(i);
                }
                return validIndices;
            }

            static (double avgSpacing, Size avgSize) CalculateValidAverages(
                List<double> spacings, List<Size> blockSizes, List<int> validIndices)
            {
                double spacingSum = 0;
                foreach (int idx in validIndices) spacingSum += spacings[idx];
                double avgSpacing = spacingSum / validIndices.Count;

                double widthSum = 0;
                double heightSum = 0;
                foreach (int idx in validIndices)
                {
                    widthSum += blockSizes[idx].Width;
                    heightSum += blockSizes[idx].Height;
                }
                Size avgSize = new Size((int)Math.Round(widthSum / validIndices.Count), (int)Math.Round(heightSum / validIndices.Count));
                return (avgSpacing, avgSize);
            }
        }

        public class VerticalLineSplitter
        {
            public List<Rect> SplitBlockIntoLines(Mat blockImage, int lineGap = 15)
            {
                Mat binary = new Mat();
                if (blockImage.Channels() > 1) Cv2.CvtColor(blockImage, binary, ColorConversionCodes.BGR2GRAY);
                else binary = blockImage.Clone();
                Cv2.Threshold(binary, binary, 0, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);
                return GetConnectedLines(binary);
            }

            private List<Rect> GetConnectedLines(Mat binaryImage)
            {
                using Mat labels = new Mat();
                using Mat stats = new Mat();
                using Mat centroids = new Mat();
                int numLabels = Cv2.ConnectedComponentsWithStats(binaryImage, labels, stats, centroids, PixelConnectivity.Connectivity8);
                var lines = new List<Rect>();
                for (int i = 1; i < numLabels; i++)
                {
                    lines.Add(new Rect(stats.At<int>(i, 0), stats.At<int>(i, 1), stats.At<int>(i, 2), stats.At<int>(i, 3)));
                }
                lines.Sort((a, b) => a.Y.CompareTo(b.Y));
                return lines;
            }
        }

        public class 小题
        {
            private const int BinaryThreshold = 180;
            private const double FillRatioThreshold = 0.4;
            private Size blockSize;
            private double spacing;
            public double AverageBlobSize { get; private set; }
            public int QuestionCount { get; private set; }
            public List<int> OptionsPerQuestion { get; private set; } = new List<int>();
            public List<string> Answers { get; private set; } = new List<string>();

            public 小题(Size blockSize, double spacing)
            {
                this.blockSize = blockSize;
                this.spacing = spacing;
                this.AverageBlobSize = (blockSize.Width + blockSize.Height) / 2.0;
            }

            public void Process(Mat image)
            {
                QuestionCount = 0;
                OptionsPerQuestion.Clear();
                Answers.Clear();
                if (image == null || image.Empty()) return;
                var lines = SplitLines(image);
                QuestionCount = lines.Count;
                foreach (var line in lines) ProcessLine(line);
            }

            private List<Mat> SplitLines(Mat image)
            {
                using var gray = new Mat();
                using var binary = new Mat();
                Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
                Cv2.Threshold(gray, binary, BinaryThreshold, 255, ThresholdTypes.BinaryInv);
                var projection = new int[image.Height + 1];
                for (int y = 0; y < image.Height; y++)
                {
                    using var row = binary.Row(y);
                    projection[y] = row.CountNonZero();
                }

                int minLineHeight = (int)(blockSize.Height * 0.6);
                int maxLineHeight = (int)(blockSize.Height * 2.5);
                int minPeakValue = (int)(blockSize.Height * 0.5);
                int maxGap = (int)(spacing * 0.7);
                var peakRegions = FindPeakRegions(projection, minPeakValue);
                var lines = new List<Mat>();

                foreach (var region in peakRegions)
                {
                    int height = region.EndY - region.StartY + 1;
                    if (height >= minLineHeight && height <= maxLineHeight)
                    {
                        var lineRoi = new Rect(0, region.StartY, image.Width, height);
                        lines.Add(new Mat(image, lineRoi));
                    }
                }
                return lines;
            }

            private List<(int StartY, int EndY)> FindPeakRegions(int[] projection, int minPeakValue)
            {
                var regions = new List<(int, int)>();
                bool inPeak = false;
                int peakStart = 0;

                for (int y = 0; y < projection.Length; y++)
                {
                    if (projection[y] >= minPeakValue && !inPeak)
                    {
                        peakStart = y;
                        inPeak = true;
                    }
                    else if (projection[y] < minPeakValue && inPeak)
                    {
                        regions.Add((peakStart, y - 1));
                        inPeak = false;
                    }
                }
                return regions;
            }

            private void ProcessLine(Mat lineImage)
            {
                if (lineImage.Empty())
                {
                    OptionsPerQuestion.Add(0);
                    Answers.Add("M");
                    return;
                }
                int optionCount = CalculateOptionCount(lineImage.Width, out double 比例);
                OptionsPerQuestion.Add(optionCount);
                string answer = RecognizeAnswerWithPosition(lineImage, optionCount, 比例);
                Answers.Add(answer);
            }

            private int CalculateOptionCount(int lineWidth, out double 比例)
            {
                double totalUnit = blockSize.Width + spacing;
                比例 = (lineWidth + spacing) / totalUnit;
                int optionCount = (int)Math.Round((lineWidth + spacing) / totalUnit) - 1;
                return Math.Max(2, optionCount);
            }

            private string RecognizeAnswerWithPosition(Mat lineImage, int optionCount, double 比例)
            {
                double 修正 = 1.02;//= 比例 - Math.Round(比例); //可能是正数也可能是附属
                using var gray = new Mat();
                var binary = new Mat();
                if (lineImage.Channels() == 3) Cv2.CvtColor(lineImage, gray, ColorConversionCodes.BGR2GRAY);
                else binary = lineImage.Clone();
                if (lineImage.Channels() == 3) Cv2.Threshold(gray, binary, BinaryThreshold, 255, ThresholdTypes.BinaryInv);

                double totalUnit = (blockSize.Width + spacing);
                double startX = (lineImage.Width - (optionCount * totalUnit * 修正) - spacing / 4);
                var filledOptions = new List<int>();

                for (int i = 0; i < optionCount; i++)
                {
                    double x = (startX + i * totalUnit) * 修正;
                    var blockRoi = new Rect((int)(x + spacing), (lineImage.Height - blockSize.Height) / 2, (int)(blockSize.Width * 修正), blockSize.Height);
                    if (blockRoi.Height > binary.Height) blockRoi.Height = binary.Height;
                    if (blockRoi.Y < 0) blockRoi.Y = 0;
                    if (blockRoi.Width > binary.Width - blockRoi.X) blockRoi.Width = binary.Width - blockRoi.X;

                    using var blockMat = new Mat(binary, blockRoi);
                    double fillRatio = (double)blockMat.CountNonZero() / (blockMat.Width * blockMat.Height);
                    if (fillRatio > FillRatioThreshold) filledOptions.Add(i);
                }

                string ans = "";
                for (int i = 0; i < filledOptions.Count; i++) ans += (char)('A' + filledOptions[i]);
                return ans;
            }
        }
        #endregion
    }
    // 扩展方法用于计算中位数
    public static class EnumerableExtensions
    {
        public static float Median(this IEnumerable<float> source)
        {
            var sorted = source.OrderBy(n => n).ToArray();
            int count = sorted.Length;

            if (count == 0) return 0;
            if (count % 2 == 0)
            {
                return (sorted[count / 2 - 1] + sorted[count / 2]) / 2f;
            }
            return sorted[count / 2];
        }
    }
}